package com.engine.salary.util;

import cn.hutool.core.util.StrUtil;
import com.engine.salary.common.LocalDateRange;
import com.engine.salary.exception.SalaryRunTimeException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.jetbrains.annotations.NotNull;
import weaver.general.BaseBean;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @description: 时间工具类
 * @author: xiajun
 * @modified By: xiajun
 * @date: Created in 10/19/21 4:23 PM
 * @version:v1.0
 */
@Slf4j
public class SalaryDateUtil {

    public static final ZoneId CTT = ZoneId.of(ZoneId.SHORT_IDS.get("CTT"));
    public static final ZoneOffset SHANGHAI_ZONE_OFF_SET = ZoneOffset.ofHours(8);

    public static final FastDateFormat DATE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd");
    public static final FastDateFormat DATETIME_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");

    public static final DateTimeFormatter YEAR_FORMATTER = DateTimeFormatter.ofPattern("yyyy");
    public static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM");
    public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static final String MONTH_FORMATTER_PATTERN = "yyyy-MM";
    public static final String DATE_FORMATTER_PATTERN = "yyyy-MM-dd";
    public static final String DATE_TIME_FORMATTER_PATTERN = "yyyy-MM-dd HH:mm:ss";
    /**
     * yyyy-MM
     **/
    private static final String MONTH_REGEX = "^([1-9]\\d{3})-(([0][1-9])|([1][0-2]))$";
    /**
     * yyyy-MM-dd
     **/
    private static final String DAY_REGEX = "^[1-9]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$";
    /**
     * 含斜杠日期格式
     */
    private static final String DAY_BAR_REGEX = "^[1-9]\\d{3}/([1-9]|1[0-2])/([1-9]|[1-2][0-9]|3[0-1])$";

    public static Long localDate2EpochMilli(LocalDate localDate) {
        if (localDate == null) {
            return NumberUtils.LONG_ZERO;
        }
        return localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();
    }

    public static Long localDateTime2EpochMilli(LocalDateTime localDateTime) {
        if (localDateTime == null) {
            return NumberUtils.LONG_ZERO;
        }
        return localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
    }

    public static Long localDateTime2EpochMilli(Date localDateTime) {
        if (localDateTime == null) {
            return NumberUtils.LONG_ZERO;
        }
        return localDateTime.getTime();
    }

    public static String getFormatYearMonth(LocalDate localDate) {
        if (localDate == null) {
            return StringUtils.EMPTY;
        }
        try {
            return localDate.format(MONTH_FORMATTER);
        } catch (Exception e) {
            log.warn("格式化月份错误", e);
            return StringUtils.EMPTY;
        }
    }


    public static String getFormatYearMonth(Date localDate) {
        if (localDate == null) {
            return StringUtils.EMPTY;
        }
        try {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(MONTH_FORMATTER_PATTERN);
            return simpleDateFormat.format(localDate);
        } catch (Exception e) {
            log.warn("格式化月份错误", e);
            return StringUtils.EMPTY;
        }
    }

    public static String getFormatYearMonth(YearMonth localDate) {
        if (localDate == null) {
            return StringUtils.EMPTY;
        }
        return localDate.format(DateTimeFormatter.ofPattern("yyyy-MM"));

    }

    public static String getFormatLocalDate(LocalDate localDate) {
        if (localDate == null) {
            return StringUtils.EMPTY;
        }
        try {
            return localDate.format(DATE_FORMATTER);
        } catch (Exception e) {
            log.warn("格式化日期错误", e);
            return StringUtils.EMPTY;
        }
    }

    public static String getFormatDate(Date localDate) {
        if (localDate == null) {
            return StringUtils.EMPTY;
        }
        try {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_FORMATTER_PATTERN);
            return simpleDateFormat.format(localDate);
        } catch (Exception e) {
            log.warn("格式化日期错误", e);
            return StringUtils.EMPTY;
        }
    }

    public static String getFormatLocalDate(LocalDateTime localDateTime) {
        if (localDateTime == null) {
            return StringUtils.EMPTY;
        }
        try {
            return localDateTime.format(DATE_FORMATTER);
        } catch (Exception e) {
            log.warn("格式化日期错误", e);
            return StringUtils.EMPTY;
        }
    }

    public static String getFormatLocalDateTime(LocalDateTime localDateTime) {
        if (localDateTime == null) {
            return StringUtils.EMPTY;
        }
        try {
            return localDateTime.format(DATE_TIME_FORMATTER);
        } catch (Exception e) {
            log.warn("格式化日期错误", e);
            return StringUtils.EMPTY;
        }
    }

    public static String getFormatLocalDateTime(Date localDateTime) {
        if (localDateTime == null) {
            return StringUtils.EMPTY;
        }
        try {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_TIME_FORMATTER_PATTERN);
            return simpleDateFormat.format(localDateTime);
        } catch (Exception e) {
            log.warn("格式化日期错误", e);
            return StringUtils.EMPTY;
        }
    }

    public static LocalDateTime dateToLocalDateTime(Date date) {
        Instant instant = date.toInstant();
        ZoneId zone = ZoneId.systemDefault();
        return LocalDateTime.ofInstant(instant, zone);
    }

    public static LocalDate dateToLocalDate(Date date) {
        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
    }

    public static Date localDateToDate(LocalDate localDate) {
        if (null == localDate) {
            return null;
        }
        ZonedDateTime zonedDateTime = localDate.atStartOfDay(ZoneId.systemDefault());
        return Date.from(zonedDateTime.toInstant());
    }

    public static Date localDateTimeToDate(LocalDateTime localDateTime) {
        if (null == localDateTime) {
            return null;
        }
        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
    }

    public static String getFormatLocalDate(Date date) {
        if (date == null) {
            return StringUtils.EMPTY;
        }
        LocalDateTime localDateTime = dateToLocalDateTime(date);
        return getFormatLocalDate(localDateTime);
    }

    public static YearMonth localDate2YearMonth(Date localDate) {
        if (localDate == null) {
            return null;
        }
        Calendar c = Calendar.getInstance();
        c.setTime(localDate);
        int year = c.get(Calendar.YEAR);
        int month = c.get(Calendar.MONTH) + 1;
        return YearMonth.of(year, month);
    }

    public static YearMonth String2YearMonth(String localDate) {
        if (checkDay(localDate)) {
            return null;
        }

        return YearMonth.parse(localDate);
    }

    public static Integer date2Year(Date localDate) {
        if (localDate == null) {
            return null;
        }
        Calendar c = Calendar.getInstance();
        c.setTime(localDate);
        int year = c.get(Calendar.YEAR);
        return year;
    }

    public static LocalDateRange localDate2Range(Date localDate) {
        if (localDate == null) {
            return null;
        }
        return LocalDateRange.builder()
                .fromDate(getFirstDayDateOfMonth(localDate))
                .endDate(getLastDayOfMonth(localDate))
                .build();
    }

    public static LocalDateRange localDate2YearRange(Date localDate) {
        if (localDate == null) {
            return null;
        }
        return LocalDateRange.builder()
                .fromDate(getFirstDayDateOfYear(localDate))
                .endDate(getLastDayOfYear(localDate))
                .build();
    }

    public static Date getFirstDayDateOfMonth(final Date date) {
        final Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        final int last = cal.getActualMinimum(Calendar.DAY_OF_MONTH);
        cal.set(Calendar.DAY_OF_MONTH, last);
        return cal.getTime();
    }

    public static Date getFirstDayDateOfMonthWithMinutesAndSeconds(final Date date) {
        final Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        final int last = cal.getActualMinimum(Calendar.DAY_OF_MONTH);
        cal.set(Calendar.DAY_OF_MONTH, last);
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }

    public static Date getLastDayOfMonth(final Date date) {
        final Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        final int last = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
        cal.set(Calendar.DAY_OF_MONTH, last);
        return cal.getTime();
    }

    public static Date getFirstDayDateOfYear(final Date date) {
        final Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        final int last = cal.getActualMinimum(Calendar.DAY_OF_YEAR);
        cal.set(Calendar.DAY_OF_YEAR, last);
        return cal.getTime();
    }

    public static Date getLastDayOfYear(final Date date) {
        final Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        final int last = cal.getActualMaximum(Calendar.DAY_OF_YEAR);
        cal.set(Calendar.DAY_OF_YEAR, last);
        return cal.getTime();
    }


    public static String getMonthBegin(String specifiedDay) {
        int year;
        int month;
        Pattern pattern = Pattern.compile("\\d+-\\d+");
        Matcher matcher = pattern.matcher(specifiedDay);
        if (StringUtils.isEmpty(specifiedDay) || !matcher.matches()) {
            return null;
        } else {
            year = Integer.parseInt(specifiedDay.split("-")[0]);
            month = Integer.parseInt(specifiedDay.split("-")[1]);
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR, year);
        calendar.set(Calendar.MONTH, month - 1);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        Date startDate = calendar.getTime();
        return sdf.format(startDate);
    }

    public static String getYearMonth(int yearNum, int monthNum) {
        LocalDateTime dateTime = LocalDateTime.now();
        int year = dateTime.getYear() + yearNum;
        int month = dateTime.getMonthValue() + monthNum;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR, year);
        calendar.set(Calendar.MONTH, month - 1);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        Date startDate = calendar.getTime();
        return sdf.format(startDate);
    }

    /**
     * 检查年月格式
     *
     * @param yearMonth
     * @return
     */
    public static boolean checkYearMonth(String yearMonth) {
        return Pattern.matches(MONTH_REGEX, yearMonth);
    }

    /**
     * 检查日期格式
     *
     * @param day
     * @return
     */
    public static boolean checkDay(String day) {
        return Pattern.matches(DAY_REGEX, day) || Pattern.matches(DAY_BAR_REGEX, day);
    }

    public static Date parse(String date, String pattern) {
        SimpleDateFormat format = new SimpleDateFormat(pattern);
        try {
            return format.parse(date);
        } catch (ParseException e) {
            new BaseBean().writeLog(String.format("日期解析异常:  %s,  %s", date, pattern));
        }
        return null;
    }


    /**
     * LocalDate转YearMonth
     *
     * @param localDate
     * @return
     */
    public static YearMonth toYearMonth(LocalDate localDate) {
        Objects.requireNonNull(localDate, "localDate");
        return YearMonth.of(localDate.getYear(), localDate.getMonthValue());
    }


    /**
     * YearMonth转Date
     * 注意dayOfMonth范围：1到31之间，最大值根据月份确定特殊情况，如2月闰年29，非闰年28
     * 如果要转换为当月最后一天，可以使用下面方法：toDateEndOfMonth(YearMonth)
     *
     * @param yearMonth
     * @param dayOfMonth
     * @return
     */
    public static Date toDate(YearMonth yearMonth, int dayOfMonth) {
        Objects.requireNonNull(yearMonth, "yearMonth");
        return localDateToDate(yearMonth.atDay(dayOfMonth));
    }

    /**
     * YearMonth转Date，转换为当月第一天
     *
     * @param yearMonth
     * @return
     */
    public static Date toDateStartOfMonth(YearMonth yearMonth) {
        return toDate(yearMonth, 1);
    }

    /**
     * YearMonth转Date，转换为当月最后一天
     *
     * @param yearMonth
     * @return
     */
    public static Date toDateEndOfMonth(YearMonth yearMonth) {
        Objects.requireNonNull(yearMonth, "yearMonth");
        return localDateToDate(yearMonth.atEndOfMonth());
    }


    /**
     * YearMonth转LocalDate
     * 注意dayOfMonth范围：1到31之间，最大值根据月份确定特殊情况，如2月闰年29，非闰年28
     * 如果要转换为当月最后一天，可以使用下面方法：toLocalDateEndOfMonth(YearMonth)
     *
     * @param yearMonth
     * @param dayOfMonth
     * @return
     */
    public static LocalDate toLocalDate(YearMonth yearMonth, int dayOfMonth) {
        Objects.requireNonNull(yearMonth, "yearMonth");
        return yearMonth.atDay(dayOfMonth);
    }

    /**
     * YearMonth转LocalDate，转换为当月第一天
     *
     * @param yearMonth
     * @return
     */
    public static LocalDate toLocalDateStartOfMonth(YearMonth yearMonth) {
        return toLocalDate(yearMonth, 1);
    }

    /**
     * YearMonth转LocalDate，转换为当月最后一天
     *
     * @param yearMonth
     * @return
     */
    public static LocalDate toLocalDateEndOfMonth(YearMonth yearMonth) {
        Objects.requireNonNull(yearMonth, "yearMonth");
        return yearMonth.atEndOfMonth();
    }

    /**
     * String转Date
     *
     * @param date
     * @return
     */
    public static Date stringToDateTime(String date) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        Date parse = null;
        if (date != null) {
            try {
                parse = sdf.parse(date);
            } catch (ParseException e) {
                throw new RuntimeException(e);
            }
        }
        return parse;
    }

    public static Date stringToDate(String date) {
        return dateStrToLocalDate(date);
    }

    //格式化日期
    public static String strToDateLong(String strDate) {
        Date date = new Date();
        try {
            date = new SimpleDateFormat("yyyyMMddHHmmss").parse(strDate + "000000");//先按照原格式转换为时间
        } catch (ParseException e) {
            e.printStackTrace();
        }
        String str = new SimpleDateFormat("yyyy-MM").format(date);//再将时间转换为对应格式字符串
        return str;
    }

    public static Date dateStrToLocalYearMonth(String date) {
        Date localDate = null;
        try {
            date = date.substring(0, 7);
            if (date.contains("/")) {
                SimpleDateFormat format = new SimpleDateFormat("yyyy/MM");
                localDate = format.parse(date);
            } else if (date.contains("-")) {
                SimpleDateFormat format = new SimpleDateFormat("yyyy-MM");
                localDate = format.parse(date);
            }
        } catch (Exception e) {
            log.error("日期解析异常,{}", date);
            localDate = null;
        }

        return localDate;
    }

    public static Date dateStrToLocalDate(String date) {
        Date localDate = null;
        try {
            date = date.substring(0, 10);
            if (date.contains("/")) {
                SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
                localDate = format.parse(date);
            } else if (date.contains("-")) {
                SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
                localDate = format.parse(date);
            }
        } catch (Exception e) {
            log.error("日期解析异常,{}", date);
            localDate = null;
        }

        return localDate;
    }

    public static Date dateStrToLocalTime(String date) {
        Date localDate = null;
        try {
            if (date.contains("/")) {
                SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss");
                localDate = format.parse(date);
            } else if (date.contains("-")) {
                SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
                localDate = format.parse(date);
            }
        } catch (Exception e) {
            log.error("日期解析异常,{}", date);
            localDate = null;
        }

        return localDate;
    }

    @NotNull
    public static Boolean monthInRange(String billMonth, String startMonth, String endMonth) {
        billMonth = billMonth + "-01";
        Date billMonthDate = SalaryDateUtil.dateStrToLocalDate(billMonth);
        Boolean inDataRange = true;
        if (StringUtils.isNotBlank(startMonth)) {
            startMonth = startMonth + "-01";
            Date socialStartDate = SalaryDateUtil.dateStrToLocalDate(startMonth);
            if(socialStartDate == null) {
                throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(0, "年月解析异常，请检查档案中相关数据设置") + ":" + startMonth.substring(0, startMonth.length() - 3));
            } else if (billMonthDate.before(socialStartDate)) {
                inDataRange = false;
            }
        }
        if (StringUtils.isNotBlank(endMonth)) {
            endMonth = endMonth + "-01";
            Date socialEndDate = SalaryDateUtil.dateStrToLocalDate(endMonth);
            if(socialEndDate == null) {
                throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(0, "年月解析异常，请检查档案中相关数据设置") + ":" + endMonth.substring(0, endMonth.length() - 3));
            } else if (billMonthDate.after(socialEndDate)) {
                inDataRange = false;
            }
        }
        return inDataRange;
    }

    /**
     * 转换时间对象
     *
     * @param dateTime LocalDateTime
     * @return Date
     * @see SalaryDateUtil#toDate(LocalDateTime, String)
     */
    public static Date toDate(LocalDateTime dateTime) {
        return toDate(dateTime, null);
    }


    /**
     * 转换时间对象
     *
     * @param dateTime LocalDateTime
     * @param offset   时区，e.g. +8
     * @return Date
     */
    public static Date toDate(LocalDateTime dateTime, String offset) {
        if (dateTime == null) {
            return null;
        }
        ZoneId zoneOffset = StrUtil.isNotEmpty(offset) ? ZoneOffset.of(offset) : ZoneOffset.systemDefault();
        return Date.from(dateTime.atZone(zoneOffset).toInstant());
    }

    public static Date plusMonths(Date date, int i) {
        LocalDate localDate = SalaryDateUtil.dateToLocalDate(date).plusMonths(i);
        return SalaryDateUtil.localDateToDate(localDate);
    }
}


